iT邦幫忙

2024 iThome 鐵人賽

DAY 8
0

前言

前篇文章介紹了一個後端系統使用三層式架構大概應該要長的樣子,這篇文章會補齊裡面所需的程式碼。

Entity

以下是本次 Entity 會用到的程式碼,本次的後端流程為 先註冊,註冊完後會傳送驗證碼,使用者獲取驗證碼後要再輸入相關資料驗證

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.*;

// 標示這個 Class 為 Entity
@Entity
// 為資料表命名
@Table(name = "_user")
// Lombok 提供的註解,可以加速開發過程
@Builder
@Data
// 為全部的變數建立建構元
@AllArgsConstructor
// 建立一個空的建構元
@NoArgsConstructor
public class UserEntity {

    // 當標示為 Entity時,就必須要有一個變數被註解為 ID,也就是設定主Key
    @Id
    // 設定新增資料時 id 會自動累加
    @GeneratedValue
    private long id;
    // 標記新增資料時此欄位的資料不得為空
    @NonNull
    private String name;
    @NonNull
    private String password;
    @NonNull
    private String email;
    private String verifyCode;
    private boolean isVerified = false;
}

Controller

這裡會是我們 API 方法的地方,這個設計使用到 ResponseEntity 來設定回傳資料的型態,ResponseEntity 可以幫忙將回傳的內容設定為 Json 格式,再丟給前端使用

大概流程就像這樣,Controller 負責接收指令,接到了再根據指令的路徑去分配使用到的功能,回傳內容我們交給 Service去回傳,因此 return 就是完全以 service 回傳回來的資料為主,這裡不做任何邏輯處理的工作

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

// 標記為 Controller
@RestController
// 設定路徑
@RequestMapping("api/user")
// 建立一個會自動幫使用 final 標記的變數建立一個建構元
@RequiredArgsConstructor
public class UserController {
    // 呼叫 Service 進行資料判斷、邏輯處理
    private final UserService userService;

    // 設定 RESTful API 跟細項路徑
    @PostMapping("/register")
    public ResponseEntity<UserResponse> register(@RequestBody UserRequest request){
        return ResponseEntity.ok(userService.register(request));
    }

    @PostMapping("/sendVerifyCode")
    public ResponseEntity<UserResponse> sendVerifyCode(@RequestBody UserRequest request){
        return ResponseEntity.ok(userService.sendVerifyCode(request));
    }

    @PostMapping("/auth")
    public ResponseEntity<UserResponse> authentication(@RequestBody UserRequest request){
        return ResponseEntity.ok(userService.authentication(request));
    }
    
}

Service

service 設定的部分比較長,我們先拆分每一項功能,最後再附上完整程式碼

框架

跟前面 Controller 一樣,我們會用一個註解去標記這個 class 是 service

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
}

register

在這個功能我們要完成的工作是新增資料表,為了避免資料有重複的問題,我們先判斷這個資料是否已經要帳號註冊過,如果沒有才會進行新增資料表的工作,這個部份我們也可以透過在 Entity 對指定的欄位設定不可重複來實現。

    public UserResponse register(UserRequest request) {
        var response = UserResponse.builder();
        try {
            String userName = request.userName();

            // 判斷此帳號是否已被註冊過
            if (userRepository.findByName(userName).isEmpty()) {
                // 新增資料表的方法
                var user = UserEntity.builder()
                        .name(userName)
                        .email(request.email())
                        .password(request.password())
                        .build();

                // 永遠記得當資料表資料變動時,要儲存這個資料表
                userRepository.save(user);

                // 撰寫回傳內容
                response.status("1");
                response.message("The account registration is successful.");
            } else {
                // 撰寫回傳內容
                response.status("0");
                response.message("The account has been registered.");
            }

                // 回傳內容
            return response.build();
        } catch (Exception e) {
                // 以防有問題的例外處理,或者我們也可以寫一個全域的錯誤例外處理
                // 這次先簡單的 try catch 就好
            response.status("0");
            response.message("There is something went wrong : " + e.getMessage());

                // 回傳內容
            return response.build();
        }
    }

sendVerifyCode

在這裡我們會將驗證碼跟帳號綁定,並將驗證碼傳給使用者,使用者就必須先驗證這個帳戶,才算是完整建立整個帳號

    public UserResponse sendVerifyCode(UserRequest request) {
        var response = UserResponse.builder();
        try {
            // 先尋找是否有使用者輸入的帳戶
            String userName = request.userName();
            if (userRepository.findByName(userName).isPresent() &&
                    !request.email().isEmpty() ) {
                // 亂數生成驗證碼
                String verifyCode = generateVerifyCode();

                // 透過 repository 寫好的功能更新資料庫中的資料,這種情況可以不用save,因為是透過 repository 執行的
                userRepository.updateVerifyCodeByEmail(verifyCode, request.email());

                // 回文內容設定
                response.status("1");
                response.message("Success");
                response.verifyCode(verifyCode);

            } else {
                // 回文內容設定
                response.status("0");
                response.message("Please check all the data and try again.");
            }

            // 回傳回文
            return response.build();
        } catch (Exception e) {
            // 回文內容設定
            response.status("0");
            response.message("There is something went wrong : " + e.getMessage());

            return response.build();
        }
    }

    // 亂數生成驗證碼
    private String generateVerifyCode() {
        int verifyCode = new Random(System.currentTimeMillis()).nextInt(100000,999999);
        return String.valueOf(verifyCode);
    }

auth

這裡負責的工作是比對使用者輸入的驗證碼跟上一個發送驗證碼時,設定的是否是一致的,如果是一致的就會再設定對應帳號的 isVarify 欄位為 true,代表這個帳號已經完成認證,就可以接續執行其他功能,

    public UserResponse authentication(UserRequest request) {
        var response = UserResponse.builder();
        try {
            if (userRepository.findByName(request.userName()).isPresent()) {
                UserEntity user = userRepository.findByName(request.userName()).orElseThrow();

                // 判斷帳戶儲存的驗證碼是否跟使用者傳入的一致
                if (user.getVerifyCode().equals(request.verifyCode())){
                    user.setVerified(true);
                    
                    userRepository.save(user);
                    
                    // 回文內容設定
                    response.status("1");
                    response.message("Welcome ! You are logged in successfully.");
                } else {
                    // 回文內容設定
                    response.status("0");
                    response.message("Please check your verify code and try again.");
                }
            } else {
                // 回文內容設定
                response.status("0");
                response.message("Please check your username and try again.");
            }
            return response.build();
        } catch (Exception e) {
                // 回文內容設定
            response.status("0");
            response.message("There is something went wrong : " + e.getMessage());

                // 回傳回文
            return response.build();
        }
    }

Repository

在 service 我們有用到一些控制資料庫的功能,但是 service 本身是沒辦法直接使用資料庫的,所以我們會透過 repository 去撰寫一些想要使用的方法,來達到使用資料庫的效果

// 標記為 Repository
@Repository
// 繼承 JpaRepository 並指定這個 Repositorty可以互動的資料庫,以及這個資料庫的 主key 是什麼型態
public interface UserRepository extends JpaRepository<UserEntity, Long> {

    // 使用 Spring Data Jpa 的關鍵字建立方法快速建立功能
    Optional<UserEntity> findByName(String username);

    @Transactional
    @Modifying
    // 使用 SQL 語法客製化自己的功能
    @Query("update UserEntity u set u.verifyCode = :verifyCode where u.email = :email")
    void updateVerifyCodeByEmail(String verifyCode, String email);
}

Request 和 Response

本次範例中使用的是 record 而不是 class 來建立 Request跟Response,先說結論,這兩種方式都是常用的方法,只是 record 撰寫上會比較簡潔,所以這次使用 record做

public record UserRequest(
        String userName,
        String email,
        String password,
        String verifyCode
) {
}

public record UserResponse(
        String status,
        String message,
        String verifyCode
) {
}

建立完畢

到此所有東西都設定完畢,我們就可以啟動服務,並且透過 postman 來測試是否成功囉~

點選這個綠色箭頭就可以開啟服務了
image

看到這段就代表成功建立服務了,port號預設為8080,這邊我有設定過所以才會是9000,這個就看自己方便設定就好
image

測試

postman 的使用方式這邊就不贅述了,直接放上測試結果
image

image

image

這個時候回去看建立好的資料表,就會發現資料建立好囉
image

總結

本篇文章補上程式碼的部分,並且使用 postman 進行測試,下篇文章會稍微修改一些程式碼,並且製作一個簡單的 APP 與我們架設的後端系統對接。


上一篇
[DAY 07] 建立第一個簡單的後端系統 1
下一篇
[DAY 09] 後端結合前端的應用
系列文
智慧語義互動平台:基於Spring和Semantic Kernel的Android應用創新30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言